今天來介紹 Haskell 的基礎語法,我們可以先用 ghci 來試著運行 Haskell 語法看看。
只要在 terminal 輸入
ghci
然後我們就可以利用這個環境來讓我們簡單的使用 Haskell 。

如果要退出 gchi 只要輸入 :q 即可

常見的運算子就跟我們平常使用的程式語言一樣:
2 + 2 -- 4
2 - 2 -- 0
3 * 2 -- 6
3 / 2 -- 1.5
True || False -- True
True && False -- False
5 == 5 -- True
5 /= 2 -- False
5 >= 2 -- True
5 <= 2 -- False
"hello" == "hello" -- True
"he" ++ "llo" -- hello
如果是寫習慣js的讀者可能會想試試看一下我們平常的語法在 Haskell 運行起來會是怎樣,我們把數字跟布林值一起做邏輯運算的話會發生以下問題
0 || True
error:
• No instance for (Num Bool) arising from the literal ‘0’
• In the second argument of ‘(||)’, namely ‘0’
In the expression: True || 0
In an equation for ‘it’: it = True || 0
這裡會告訴我們 || 運算子並不能傳入 0 ,因為 Haskell 是一個強型別語言所以並不會自動地幫我轉換數值的型別。
在 Haskell 宣告變數我們只要 name = expression 就可以了
x = 5
x -- 5
x = 2 + 2
x -- 4
x = "1" ++ "23"
x -- "123"
let 在 Haskell 中是用來宣告區域變數所使用我們可以搭配 in 來限制這個區域變數所在的scope
這邊為了較好示範先寫一個 .hs 檔
這個範例只是為了解釋 scope 的差異,所以對於
::、show、do等等沒看過的東西各位可以先無視xD
a :: Integer
a = 1
b :: Integer
b = 100
main :: IO ()
main = do
let b = 10
do
print ("b = " ++ show b)
let a = 2
in do
print ("a = " ++ show a)
print ("a + b = " ++ show (a + b))
print ("a = " ++ show a)
print ("b = " ++ show b)
print ("a + b = " ++ show (a + b))
從上面的範例我們在最上面宣告了兩個變數a 、b 然後在 main 裏面分別 let b 及 let a 但直得注意的是一個後面有接 in 另一個則沒有。
我們先來看一下這個程式碼的輸出:
"b = 10"
"a = 2"
"a + b = 12"
"a = 1"
"b = 10"
"a + b = 11"
第一個 print 很好理解,在 main 裡面我就先 let b = 10 所以第一個 print 是 10 而不是最外層的100
第二個及第三個 print 因為我們 let a = 2 所以第二個 print 就會是 2 ,然後因為還是在 main 裡面所以 a+b 就會是 12
剩下的 print 因為我們離開 let a = 2 in ... 的範圍,所以 a 就會是最外面的 1 但還是在 main 裡面所以 b 依然是 10 那 a + b 就會是 11
在 Haskell 中要宣告一個function 也很簡單只要 name args(看有幾個) = expression
add x y = x + y
add5 x = x + 5
使用上也很簡單只要在 function name 後面加上參數就可以呼叫了
add5 2 -- 7
add 5 2 -- 7
x = 10
add5 x -- 15
在 Haskell 中 function 還有很多很多很多操作,像是 infix、$ 、Currying、Higher-Order Functions ,就留到之後的文章再來好好說明了。
今天的程式碼依然會放到 github
補充
Haskell的type system非常嚴格,不同的type就是不同的type,某個function如果需要Foo type的參數,你就絕對不可能傳一個Bar type的參數進去,別的OO語言或許可以向上轉型再向下轉型來唬弄編譯器(e.g. Java可以寫成這樣(Bar) (Object) foo),但很可惜的是Haskell是純FP語言,不存在任何OO的特性。
Haskell也不存在自動轉型這種功能,別的語言有int自動轉型成long或是float轉型成double的情況,Haskell可就沒如此親切,Float就是Float、Double就是Double,不同的type湊在一起只會編譯錯誤。
上述例子的0 || True編譯錯誤訊息寫No instance for (Num Bool) arising from the literal ‘0’,這該如何解釋?
Haskell的type system非常強大但也因此讓它的錯誤訊息非常難以理解。
首先我們需要知道||的type為Bool -> Bool -> Bool,它是一個吃兩個Bool回傳一個Bool的function,再來True的type是Bool應該不難理解,那0的type是什麼?
在GHCi裡可以輸入:type 0來觀察,它應該會告訴你是Num p => p,這可以讀作「0的type是p,這個p是Num的instance」,p是type variable,可以想成是Java Generics常見的T、U、V名稱;Num是type class,可以類比OO的interface;instance可以類比OO裡實作某個interface的概念。其實這個0的type還沒確定下來,這個p可能是Int、Double等,實際的type編譯器會從上下文來推敲。
就是這個編譯器推敲type的結論造就上述的錯誤訊息,因為它把p看成是Bool,但Bool並不存在Num Bool的instance關係,所以編譯器才會告訴這你奇怪的錯誤。
非常感謝大大的補充🙏🙏🙏